/*****************************************************************************
 *
 * Copyright (C) 2001 Uppsala University and Ericsson AB.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Erik Nordstr�m, <erik.nordstrom@it.uu.se>
 *          
 *
 *****************************************************************************/

#include <time.h>

#ifdef NS_PORT
#include "aodv-uu.h"
#else
#include "defs.h"
#include "aodv_timeout.h"
#include "aodv_socket.h"
#include "aodv_neighbor.h"
#include "aodv_rreq.h"
#include "aodv_hello.h"
#include "aodv_rerr.h"
#include "timer_queue.h"
#include "debug.h"
#include "params.h"
#include "routing_table.h"
#include "packet_queue.h"
#include "k_route.h"
#include "seek_list.h"
#include "icmp.h"

#ifdef MAODV
#include "multicast_routing_table.h"
#include "maodv_mact.h"
#include "maodv_neighbor.h"
#endif /* MAODV */

extern int expanding_ring_search, local_repair;
void route_delete_timeout(void *arg);

#endif

#ifdef MAODV
static int activate_multicast_route_if_any(seek_list_t * seek_entry); 
#endif

/* These are timeout functions which are called when timers expire... */

void NS_CLASS route_discovery_timeout(void *arg)
{
    struct timeval now;
    seek_list_t *seek_entry;
    rt_table_t *rt, *repair_rt;
#ifdef MAODV
    u_int32_t dest_addr;
#endif

    seek_entry = (seek_list_t *) arg;

#define TTL_VALUE seek_entry->ttl

    /* Sanity check... */
    if (!seek_entry)
	return;

    gettimeofday(&now, NULL);

    DEBUG(LOG_DEBUG, 0, "%s", ip_to_str(seek_entry->dest_addr));

    if (seek_entry->reqs < RREQ_RETRIES) {
#ifdef MAODV
        // activate any multicast route if we already get one. 
        if(activate_multicast_route_if_any(seek_entry)) { 
            return; 
        }
#endif
	if (expanding_ring_search) {

	    if (TTL_VALUE < TTL_THRESHOLD)
		TTL_VALUE += TTL_INCREMENT;
	    else {
		TTL_VALUE = NET_DIAMETER;
		seek_entry->reqs++;
	    }
	    /* Set a new timer for seeking this destination */
	    timer_set_timeout(&seek_entry->seek_timer, RING_TRAVERSAL_TIME);
	} else {
	    seek_entry->reqs++;
	    timer_set_timeout(&seek_entry->seek_timer, 
			      seek_entry->reqs * 2 * NET_TRAVERSAL_TIME);
	}
	/* AODV should use a binary exponential backoff RREP waiting
           time. */
	DEBUG(LOG_DEBUG, 0, "Seeking %s ttl=%d wait=%d",
	      ip_to_str(seek_entry->dest_addr),
	      TTL_VALUE, 2 * TTL_VALUE * NODE_TRAVERSAL_TIME);

	/* A routing table entry waiting for a RREP should not be expunged
	   before 2 * NET_TRAVERSAL_TIME... */
	rt = rt_table_find(seek_entry->dest_addr);

	if (rt && timeval_diff(&rt->rt_timer.timeout, &now) <
	    (2 * NET_TRAVERSAL_TIME))
	    rt_table_update_timeout(rt, 2 * NET_TRAVERSAL_TIME);

        rreq_send_ext(seek_entry->dest_addr, seek_entry->dest_seqno,
            seek_entry->ttl, seek_entry->flags, seek_entry->grp_hcnt,
            seek_entry->grp_addr);
    } else {
#ifdef MAODV
        if(seek_entry->flags & RREQ_JOIN && !(seek_entry->flags & RREQ_REPAIR)) {
            mrt_table_t *group_rec;
            mrt_nexthop_t *upstream;

            DEBUG(LOG_DEBUG, 0, "In special MCAST timeout segment");

            /* try to join the group */
            group_rec = mrt_table_find(seek_entry->dest_addr);
            upstream = mact_activate_best_upstream(group_rec);

            if(upstream) {
                if(group_rec->is_broken) {
                    /* we were repairing route */
                    group_rec->is_broken = 0;
                } else if(group_rec->is_gateway) {
                    /* Gateway joining, not a member */
                } else {
                    /* we were able to select an upstream */
                    group_rec->is_member = 1;
                    DEBUG(LOG_DEBUG, 0, "Joined Group");
                }
            } else {
                /* have to become leader ourselves */
                if(group_rec->is_broken && !group_rec->is_member) {
                    /* we are a router who lost his upstream */
                    group_rec->is_broken = 0;
                    mact_make_downstream_leader(group_rec);
                    DEBUG(LOG_DEBUG, 0, "Link broke, make downstream leader.");
                } else {
                    if(group_rec->is_broken) {
                        group_rec->is_broken = 0;
                        DEBUG(LOG_DEBUG, 0, "Was broken, now fixed.");
                    }

                    mrt_table_become_leader(group_rec);
                    DEBUG(LOG_DEBUG, 0, "Became group leader");
                }
            }

            dest_addr = seek_entry->dest_addr;
            if(seek_list_remove(seek_list_find(seek_entry->dest_addr))) {
                if(mcast_packet_queue_send(dest_addr) < 0) {
                    DEBUG(LOG_DEBUG, 0, "Error dequeueing packets.");
                }
    } else {
                DEBUG(LOG_DEBUG, 0, "No longer in seek list.");
            }

            return;
        } else if((seek_entry->flags & RREQ_JOIN) &&
                (seek_entry->flags & RREQ_REPAIR)) {
            mrt_table_t *mrt;
            mrt = mrt_table_find(seek_entry->grp_addr);

            if(mrt) {
                /* Done merging, might have worked. */
                DEBUG(LOG_DEBUG, 0, "Route discovery for merge timed out. Clearing in_repair.");
                mrt->in_repair = 0;

            } else {
                DEBUG(LOG_DEBUG, 0, "Bad dest_addr:%d in seek_list.",
                        ip_to_str(seek_entry->grp_addr));
            }
        }
#endif /* MAODV */
	packet_queue_drop(seek_entry->dest_addr);

	DEBUG(LOG_DEBUG, 0, "NO ROUTE FOUND!");

#ifndef NS_PORT
	/* Send an ICMP Destination Host Unreachable to the application: */
	if (seek_entry->ipd)
	    icmp_send_host_unreachable(seek_entry->ipd->data,
				       seek_entry->ipd->len);
#endif
	repair_rt = rt_table_find(seek_entry->dest_addr);

	seek_list_remove(seek_list_find(seek_entry->dest_addr));

	/* If this route has been in repair, then we should timeout
	   the route at this point. */
	if (repair_rt && (repair_rt->flags & RT_REPAIR)) {
	    DEBUG(LOG_DEBUG, 0, "REPAIR for %s failed!",
		  ip_to_str(repair_rt->dest_addr));
	    local_repair_timeout(repair_rt);
	}
    }
}

void NS_CLASS local_repair_timeout(void *arg)
{
    rt_table_t *rt;
    u_int32_t rerr_dest = AODV_BROADCAST;
    RERR *rerr = NULL;

    rt = (rt_table_t *) arg;

    if (!rt)
	return;

    /* Unset the REPAIR flag */
    rt->flags &= ~RT_REPAIR;

    rt->rt_timer.handler = &NS_CLASS route_delete_timeout;
    timer_set_timeout(&rt->rt_timer, DELETE_PERIOD);

    DEBUG(LOG_DEBUG, 0, "%s removed in %u msecs",
	  ip_to_str(rt->dest_addr), DELETE_PERIOD);

    /* Route should already be invalidated. */

    if (rt->nprec) {

	rerr = rerr_create(0, rt->dest_addr, rt->dest_seqno);

	if (rt->nprec == 1) {
	    rerr_dest = FIRST_PREC(rt->precursors)->neighbor;

	    aodv_socket_send((AODV_msg *) rerr, rerr_dest, 
			     RERR_CALC_SIZE(rerr), 1,
			     &DEV_IFINDEX(rt->ifindex));
	} else {
	    int i;

	    for (i = 0; i < MAX_NR_INTERFACES; i++) {
		if (!DEV_NR(i).enabled)
		    continue;
		aodv_socket_send((AODV_msg *) rerr, rerr_dest,
				 RERR_CALC_SIZE(rerr), 1, &DEV_NR(i));
	    }
	}
	DEBUG(LOG_DEBUG, 0, "Sending RERR about %s to %s",
	      ip_to_str(rt->dest_addr), ip_to_str(rerr_dest));
    }
    precursor_list_destroy(rt);
}

#ifdef MAODV
int activate_multicast_route_if_any(seek_list_t * seek_entry) 
{
    if(seek_entry->flags & RREQ_JOIN && !(seek_entry->flags & RREQ_REPAIR)) {
        mrt_table_t *group_rec;
        mrt_nexthop_t *upstream;
    
        DEBUG(LOG_DEBUG, 0, "In has_multicast_route()");
    
        /* try to join the group */
        group_rec = mrt_table_find(seek_entry->dest_addr);
        if(group_rec == NULL) { 
            return 0; 
        }
        upstream = mact_activate_best_upstream(group_rec);
        if (!upstream) { 
            return 0; 
        }
        // we have activate upstream node and flush the packets in queue,
        // now update the routing table.            
        if(group_rec->is_broken) {
            /* we were repairing route */
            group_rec->is_broken = 0;
        } else if(group_rec->is_gateway) {
            /* Gateway joining, not a member */
        } else {
            /* we were able to select an upstream */
            group_rec->is_member = 1;
            DEBUG(LOG_DEBUG, 0, "Joined Group");
        }
        u_int32_t dest_addr = seek_entry->dest_addr;
        if(seek_list_remove(seek_list_find(seek_entry->dest_addr))) {
            if(mcast_packet_queue_send(dest_addr) < 0) {
                DEBUG(LOG_DEBUG, 0, "Error dequeueing packets.");
            }
        } else {
            DEBUG(LOG_DEBUG, 0, "No longer in seek list.");
        }   
        return 1;
    } 
    return 0; 
}

#endif

#ifdef MAODV
void NS_CLASS multicast_route_expire_timeout( void * arg )
{
    mrt_table_t *mrt;

    mrt = (mrt_table_t *) arg;

    if ( !mrt ) {
        log( LOG_WARNING, 0, __FUNCTION__, "arg was NULL, ignoring timeout!" );
        return;
    }

    DEBUG( LOG_DEBUG, 0, "Multicast Route %s DOWN, seqno = %d",
        ip_to_str( mrt->grp_addr ), mrt->grp_seqno );

/*
    CHECK: Remove after creating valid flag.

    mrt_table_invalidate( mrt );
    mrt_nexthop_remove_all( mrt );
*/

    return;
}
#endif

void NS_CLASS route_expire_timeout(void *arg)
{
    rt_table_t *rt;

    rt = (rt_table_t *) arg;

    if (!rt) {
	log(LOG_WARNING, 0, __FUNCTION__, "arg was NULL, ignoring timeout!");
	return;
    }

    DEBUG(LOG_DEBUG, 0, "Route %s DOWN, seqno=%d",
	  ip_to_str(rt->dest_addr), rt->dest_seqno);

#ifdef MAODV
    // If the neighboring link broke, try to repair the tree. 
    if (rt->hcnt == 1) { 
        mcast_neighbor_link_break(rt);
    }
#endif

    rt_table_invalidate(rt);
    precursor_list_destroy(rt);

    return;
}

void NS_CLASS route_delete_timeout(void *arg)
{
    rt_table_t *rt;

    rt = (rt_table_t *) arg;

    /* Sanity check: */
    if (!rt)
	return;

    DEBUG(LOG_DEBUG, 0, "%s", ip_to_str(rt->dest_addr));

    /* The kernel route entry is already deleted, so we only delete the
       internal AODV routing entry... */
    rt_table_delete(rt->dest_addr);
    
}

/* This is called when we stop receiveing hello messages from a
   node. For now this is basically the same as a route timeout. */
void NS_CLASS hello_timeout(void *arg)
{
    rt_table_t *rt;
    struct timeval now;

    rt = (rt_table_t *) arg;
    
    if (!rt)
	return;

    gettimeofday(&now, NULL);

    DEBUG(LOG_DEBUG, 0, "LINK/HELLO FAILURE %s last HELLO: %d",
	  ip_to_str(rt->dest_addr), timeval_diff(&now, &rt->last_hello_time));

    if (rt && rt->state == VALID && !(rt->flags & RT_UNIDIR)) {

	/* If the we can repair the route, then mark it to be
	   repaired.. */
	if (local_repair && rt->hcnt <= MAX_REPAIR_TTL) {
	    rt->flags |= RT_REPAIR;
	    DEBUG(LOG_DEBUG, 0, "Marking %s for REPAIR",
		  ip_to_str(rt->dest_addr));
#ifdef NS_PORT
	    /* Buffer pending packets from interface queue */
	    interfaceQueue((nsaddr_t) rt->dest_addr, IFQ_BUFFER);
#endif
	}
	neighbor_link_break(rt);
#ifdef MAODV
        mcast_neighbor_link_break(rt);
#endif
    }
}

void NS_CLASS rrep_ack_timeout(void *arg)
{
    rt_table_t *rt;

    /* We must be really sure here, that this entry really exists at
       this point... (Though it should). */
    rt = (rt_table_t *) arg;
    
    if (!rt)
	return;
    
    /* When a RREP transmission fails (i.e. lack of RREP-ACK), add to
       blacklist set... */
    rreq_blacklist_insert(rt->dest_addr);

    DEBUG(LOG_DEBUG, 0, "%s", ip_to_str(rt->dest_addr));
}

void NS_CLASS wait_on_reboot_timeout(void *arg)
{
    *((int *) arg) = 0;

    DEBUG(LOG_DEBUG, 0, "Wait on reboot over!!");
}

void NS_CLASS packet_queue_timeout(void *arg)
{
    packet_queue_garbage_collect();
    timer_set_timeout(&PQ.garbage_collect_timer, GARBAGE_COLLECT_TIME);
}
